{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The `__prepare__` Method"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We know that when we create a class, the metaclass `__new__` method is invoked with an argument (`cls_dict`) for the class dictionary.\n",
    "\n",
    "It is not in fact an empty dictionary at first:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __new__(mcls, name, bases, cls_dict, **kwargs):\n",
    "        print('MyMeta.__new__ called...')\n",
    "        print('\\tcls: ', mcls, type(mcls))\n",
    "        print('\\tname:', name, type(name))\n",
    "        print('\\tbases: ', bases, type(bases))\n",
    "        print('\\tcls_dict:', cls_dict, type(cls_dict))\n",
    "        print('\\tkwargs:', kwargs)\n",
    "        return super().__new__(mcls, name, bases, cls_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MyMeta.__new__ called...\n",
      "\tcls:  <class '__main__.MyMeta'> <class 'type'>\n",
      "\tname: MyClass <class 'str'>\n",
      "\tbases:  () <class 'tuple'>\n",
      "\tcls_dict: {'__module__': '__main__', '__qualname__': 'MyClass'} <class 'dict'>\n",
      "\tkwargs: {}\n"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=MyMeta):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, as we see, `cls_dict` is a dictionary and it also contains some information already. It is obviously being created somewhere before being passed to the `__new__` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The class dictionary is actually created by calling the `__prepare__` method, which the `type` class implements.\n",
    "\n",
    "When the class is created, Python calls `__prepare__` and uses the return value of that method as the initialized class dictionary.\n",
    "Then right before calling `__new__` it adds a few items into that dictionary, and then calls the `__new__` method using that pre-created and initialized dictionary."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since `__prepare__` is just a method in `type`, we can override it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    @staticmethod\n",
    "    def __prepare__(name, bases, **kwargs):\n",
    "        print('MyMeta.__prepare__ called...')\n",
    "        print('\\tname:', name)\n",
    "        print('\\tkwargs:', kwargs)\n",
    "        return {'a': 100, 'b': 200}\n",
    "    \n",
    "    def __new__(mcls, name, bases, cls_dict, **kwargs):\n",
    "        print('MyMeta.__new__ called...')\n",
    "        print('\\tcls: ', mcls, type(mcls))\n",
    "        print('\\tname:', name, type(name))\n",
    "        print('\\tbases: ', bases, type(bases))\n",
    "        print('\\tcls_dict:', cls_dict, type(cls_dict))\n",
    "        print('\\tkwargs:', kwargs)\n",
    "        return super().__new__(mcls, name, bases, cls_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MyMeta.__prepare__ called...\n",
      "\tname: MyClass\n",
      "\tkwargs: {'kw1': 10, 'kw2': 20}\n",
      "MyMeta.__new__ called...\n",
      "\tcls:  <class '__main__.MyMeta'> <class 'type'>\n",
      "\tname: MyClass <class 'str'>\n",
      "\tbases:  () <class 'tuple'>\n",
      "\tcls_dict: {'a': 100, 'b': 200, '__module__': '__main__', '__qualname__': 'MyClass'} <class 'dict'>\n",
      "\tkwargs: {'kw1': 10, 'kw2': 20}\n"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=MyMeta, kw1=10, kw2=20):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice how the `__prepare__` method was called **before** the `__new__` method was called."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also notice how it contains the items `'a': 100` and `'b': 200` which we injected in the `__prepare__` method.\n",
    "\n",
    "The `cls_dict` argument in `__new__` has a couple of extra items that it injects for us prior to calling the `__new__` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, if we do not specify a `__prepare__` method in our metaclass, we inherit the one that is already defined in `type` - which returns an empty dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type.__prepare__()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example where using this method can simplify things somewhat."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Recall the example where we passed named arguments to the metaclass in order to create some additional class attributes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __new__(mcls, name, bases, class_dict, **kwargs):\n",
    "        class_dict.update(kwargs)\n",
    "        return super().__new__(mcls, name, bases, class_dict)\n",
    "    \n",
    "class MyClass(metaclass=MyMeta, arg1=100, arg2=200):\n",
    "    pass        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              'arg1': 100,\n",
       "              'arg2': 200,\n",
       "              '__dict__': <attribute '__dict__' of 'MyClass' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'MyClass' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(MyClass)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We were able to override the `__new__` method and inject the additional arguments right into the class dictionary."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But we could just as easily inject those items in the class dictionary right in the `__prepare__` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What's important to understand is that whatever extra arguments we pass to the metaclass are also passed along to the `__prepare__` method, just like they are eventually passed to `__new__`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __prepare__(name, bases, **kwargs):\n",
    "        print(f'MyMeta.__prepare__ called... with {kwargs}')\n",
    "        # we could create a new dictionary and insert everything we need from kwargs\n",
    "        # or we could just use the kwargs dictionary directly\n",
    "        kwargs['bonus_attr'] = 'Python rocks!'\n",
    "        return kwargs\n",
    "    \n",
    "    def __new__(cls, name, bases, cls_dict, **kwargs):\n",
    "        print('MyMeta.__new__ called...')\n",
    "        print('\\tcls: ', cls, type(cls))\n",
    "        print('\\tname:', name, type(name))\n",
    "        print('\\tbases: ', bases, type(bases))\n",
    "        print('\\tcls_dict:', cls_dict, type(cls_dict))\n",
    "        print('\\tkwargs:', kwargs)\n",
    "        return super().__new__(cls, name, bases, cls_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MyMeta.__prepare__ called... with {'kw1': 1, 'kw2': 2}\n",
      "MyMeta.__new__ called...\n",
      "\tcls:  <class '__main__.MyMeta'> <class 'type'>\n",
      "\tname: MyClass <class 'str'>\n",
      "\tbases:  () <class 'tuple'>\n",
      "\tcls_dict: {'kw1': 1, 'kw2': 2, 'bonus_attr': 'Python rocks!', '__module__': '__main__', '__qualname__': 'MyClass'} <class 'dict'>\n",
      "\tkwargs: {'kw1': 1, 'kw2': 2}\n"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=MyMeta, kw1=1, kw2=2):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'kw1': 1,\n",
       "              'kw2': 2,\n",
       "              'bonus_attr': 'Python rocks!',\n",
       "              '__module__': '__main__',\n",
       "              '__dict__': <attribute '__dict__' of 'MyClass' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'MyClass' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(MyClass)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And as you can see, we have our class attributes, and we did not have to use `__new__`. So often, `__prepare__` is a much simpler alternative to overriding `__new__`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The return value of `__prepare__` must be a mapping type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __prepare__(name, bases):\n",
    "        return 'some string'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "string indices must be integers",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-12-63f1da96d093>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mMyClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmetaclass\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mMyMeta\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      2\u001b[0m     \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-12-63f1da96d093>\u001b[0m in \u001b[0;36mMyClass\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mMyClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmetaclass\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mMyMeta\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      2\u001b[0m     \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mTypeError\u001b[0m: string indices must be integers"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=MyMeta):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This exception is raised because Python is trying to use the class dictionary as a mapping type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "string indices must be integers",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-13-dd007ae092d9>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mcls_dict\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'some string'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mcls_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'__module__'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m: string indices must be integers"
     ]
    }
   ],
   "source": [
    "cls_dict = 'some string'\n",
    "cls_dict['__module__']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The return value must therefore be a mapping type, but it does not have to be a dict - it could be an OrderedDict for example, or even a custom dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import OrderedDict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __prepare__(name, bases):\n",
    "        d = OrderedDict()\n",
    "        d['bonus'] = 'Python rocks!'\n",
    "        return d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyClass(metaclass=MyMeta):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'bonus': 'Python rocks!',\n",
       "              '__module__': '__main__',\n",
       "              '__dict__': <attribute '__dict__' of 'MyClass' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'MyClass' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(MyClass)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or it could even be a custom dictionary type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import UserDict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CustomDict(UserDict):\n",
    "    def __setitem__(self, key, value):\n",
    "        print(f'Setting {key} = {value} in custom dictionary')\n",
    "        super().__setitem__(key, value)\n",
    "        \n",
    "    def __getitem__(self, key):\n",
    "        print(f'Getting {key} from custom dictionary')\n",
    "        return int(super().__getitem__(key))   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __prepare__(name, bases):\n",
    "        return CustomDict()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Getting __name__ from custom dictionary\n",
      "Setting __module__ = __main__ in custom dictionary\n",
      "Setting __qualname__ = MyClass in custom dictionary\n"
     ]
    },
    {
     "ename": "TypeError",
     "evalue": "type.__new__() argument 3 must be dict, not CustomDict",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-21-63f1da96d093>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mMyClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmetaclass\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mMyMeta\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      2\u001b[0m     \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mTypeError\u001b[0m: type.__new__() argument 3 must be dict, not CustomDict"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=MyMeta):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we have a slight problem here. The `__new__` method actually expects a `dict`. Even though `CustomDict` essentially behaves like a dictionary, it is not in fact a subclass of `dict`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "issubclass(CustomDict, dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But as long as our custom dictionary inherits from `dict` we should be fine:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CustomDict(dict):\n",
    "    def __setitem__(self, key, value):\n",
    "        print(f'Setting {key} = {value} in custom dictionary')\n",
    "        super().__setitem__(key, value)\n",
    "        \n",
    "    def __getitem__(self, key):\n",
    "        print(f'Getting {key} from custom dictionary')\n",
    "        return int(super().__getitem__(key))   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyMeta(type):\n",
    "    def __prepare__(name, bases):\n",
    "        return CustomDict()\n",
    "    \n",
    "    def __new__(mcls, name, bases, cls_dict):\n",
    "        print('metaclass __new__ called...')\n",
    "        print(f'\\ttype(cls_dict) = {type(cls_dict)}')\n",
    "        print(f'\\tcls_dict={cls_dict}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Getting __name__ from custom dictionary\n",
      "Setting __module__ = __main__ in custom dictionary\n",
      "Setting __qualname__ = MyClass in custom dictionary\n",
      "metaclass __new__ called...\n",
      "\ttype(cls_dict) = <class '__main__.CustomDict'>\n",
      "\tcls_dict={'__module__': '__main__', '__qualname__': 'MyClass'}\n"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=MyMeta):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the dictionary we returned from `__prepare__` was a `CustomDict` instance that is eventually passed to `__new__` when it is called. \n",
    "\n",
    "And between `__prepare__` and `__new__`, Python accessed our dictionary to read/write a few items."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
